/*
 * $Id: BeanToJsonSerializer.java 66350 2007-08-20 21:11:41Z xluan $
 *
 * Copyright 2006- Revolution Health Group.  All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Revolution Health Group.  (Confidential Information).
 * You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license 
 * agreement you entered into with Revolution Health Group.
 *
 */
package org.javason.json;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONNull;
import net.sf.json.JSONObject;

import org.javason.jsonrpc.impl.DateConverter;
import org.javason.util.ClassUtils;
import org.javason.util.ObjectUtils;

public class BeanToJsonSerializer {
	
	private BeanToJsonSerializer() {
		ConvertUtils.register(new DateConverter(), java.util.Date.class);
	}
	
	private static final Set<Class> CONVERTABLE_CLASSES = new HashSet<Class>() {{
		add(XMLGregorianCalendar.class);
	}};

	public static JSONObject serializeToJsonObject(Object bean) {
		Object result = serialize(bean);
		JSONObject jsonObj = new JSONObject();
		try {
			if (ObjectUtils.isArray(bean) || result instanceof JSONArray) {
				jsonObj.put("array", result);
			} else if (result instanceof JSONObject){
				jsonObj = (JSONObject) result;
			} else {
				jsonObj.put(bean.getClass().getSimpleName(), result);
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return jsonObj;
	}
	
	public static Object serialize(Object bean) {
		Object result = JSONNull.getInstance();
		
		if (isObjectJson(bean)) {
			result = bean;
		} else if (bean != null && isConvertable(bean.getClass())) {
			result = ConvertUtils.convert(bean);
		} else if (bean != null && java.util.Date.class.isAssignableFrom(bean.getClass())) {
			// for date consistency, use the XMLGregorianCalendar
			result = DateConverter.toXMLGregorianCalendar((java.util.Date)bean).toString();
		} else if (bean != null && ObjectUtils.isComplex(bean) && !ObjectUtils.isArray(bean) && !ObjectUtils.isMap(bean)){
	    	JSONObject jsonObject = new JSONObject();
	    	try {
				PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors( bean );
	            for( int i = 0; i < pds.length; i++ ){
	               String key = pds[i].getName();
	               if( "class".equals( key ) ){
	                  continue;
	               }
	
	               Class type = pds[i].getPropertyType();
	               Object value = PropertyUtils.getProperty( bean, key );
				
	               
	               if (String.class.isAssignableFrom( type ) ){
	            	   jsonObject.put( key, (value == null) ? "" : value );
	               } else if (ObjectUtils.isArray(value)) {
	            	   jsonObject.put(key, serialize(value));
	               } else if (value == null) {
	            	   jsonObject.put(key, JSONNull.getInstance());
	               } else if (ObjectUtils.isSimpleType(value) || type.isEnum()) {
	            	   if (ClassUtils.isXmlEnum(type)) {
	            		   jsonObject.put(key, ObjectUtils.getXmlEnumValue(value));
	            	   } else {
	            		   jsonObject.put(key, value);
	            	   }
	               } else {
	            	   jsonObject.put(key, serialize(value));
	               }
	            }

	            result = jsonObject;
		    } catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (JSONException e) {
				e.printStackTrace();
			}
		} else if (ObjectUtils.isArray(bean)) {
			Collection collection = null;
			if (bean.getClass().isArray()) {
				collection = Arrays.asList((Object[])bean);
			} else {
				collection = (Collection) bean;
			}
			
			result = new JSONArray();
			for (Object item : collection) {
				((JSONArray)result).add(serialize(item));
			}
		} else if (ObjectUtils.isMap(bean)) {
			Map map = (Map) bean;
			result = new JSONObject();
			for (Object key : map.keySet()) {
				try {
					((JSONObject) result).put(key.toString(), serialize(map.get(key)));
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
		} else if (bean != null && ClassUtils.isXmlEnum(bean.getClass())) {
			result = ObjectUtils.getXmlEnumValue(bean);
	    } else {
	    	result = bean;
	    }
		return result;
	}
	
	private static boolean isConvertable(Class clazz) {
		boolean found = false;
		for (Class convertableClass : CONVERTABLE_CLASSES) {
			if (convertableClass.isAssignableFrom(clazz)) {
				found = true;
				break;
			}
		}
		return found;
	}
	
	private static boolean isObjectJson(Object bean) {
		if ((JSONNull.getInstance().equals(bean)) || 
			(bean instanceof JSONObject) ||
			(bean instanceof JSONArray)) {
			return true;
		}
		return false;
	}
}
